Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interfaces/home: add 'read' attribute to allow non-owner read to @{HOME} #5016

Merged
merged 8 commits into from
May 23, 2018

Conversation

jdstrand
Copy link

@jdstrand jdstrand commented Apr 9, 2018

If unspecified, 'read' defaults to 'read: owner'. When 'read: all' is
specified, deny connection and auto-connection (thus requiring a manual
review).

References:
https://forum.snapcraft.io/t/home-access-as-root-from-confined-snaps/3802

If unspecified, 'read' defaults to 'read: owner'. When 'read: all' is
specified, deny connection and auto-connection (thus requiring a manual
review).

References:
https://forum.snapcraft.io/t/home-access-as-root-from-confined-snaps/3802
@jdstrand jdstrand requested a review from niemeyer April 9, 2018 15:42
@jdstrand
Copy link
Author

jdstrand commented Apr 9, 2018

@niemeyer: FYI, I did read: [all|owner] instead of read: [all|own]. If you prefer own over owner, let me know and I'll adjust. I also set the base declaration to require a snap declaration when using read: all regardless of the value of 'classic'.

// It's fine if 'read' isn't specified, but if it is, it needs to be
// either 'all' or 'owner'
if r, ok := plug.Attrs["read"]; ok {
_, ok = r.(string)
Copy link
Collaborator

@zyga zyga Apr 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this is correct but it reads as a bit off.

r is an interface (probably interface{}) so you need a type assertion to see that it is a string.

But after the test r is still interface{} because you didn't assign the type-safe r anywhere (in pseudo-code: rs, ok := r.(string). I'm not sure what go does in the case that ok is true (so r is a string) but the comparisons use an interface on the lvalue and a string literal on the rvalue.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK Go does the right thing (although it does look 'different' from the usual pattern). Interfaces are comparable and equal if both have the same dynamic type and dynamic values are equal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic is safe, but there's another gotcha here in terms of the actual intention behind the logic: this check ignores incorrect values that have the wrong type.

We can both fix that and make the logic simpler by folding both conditionals into:

if r, ok := plug.Attrs["read"]; ok && r != "all" {
... error ...
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my point about the mishandling of the wrong type is incorrect. The simplification point remains.


func (iface *homeInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
var read string
_ = plug.Attr("read", &read)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use plug.Attr in the BeforePreparePlug above?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm doing it this way in BeforePreparePlug because I want to see if it exists at all (since it is ok if it doesn't) and if it does make sure it is a string. I could just ignore if it isn't a string, but this isn't what we do elsewhere (eg browser-support, content and docker-support). The reason I can use plug.Attr() in AppArmorConnectedPlug is because by the time it is run, I'm sure things are ok and if read isn't an attribute, then the 'read' variable is simply empty and won't match read != "all". As such, I prefer to leave the logic as is. If people feel strongly about changing it, we should change it in all of the interfaces that do similar checks.

func init() {
registerIface(&commonInterface{
registerIface(&homeInterface{commonInterface{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must say I'm really happy we're doing this. It makes to-and-from common vs custom interface transitions so much easier than before.

Copy link
Collaborator

@zyga zyga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+0.9 with some points inline.

@@ -63,14 +76,56 @@ owner /run/user/[0-9]*/gvfs/{,**} r,
owner /run/user/[0-9]*/gvfs/*/** w,
`

const homeConnectedPlugAppArmorWithAllRead = `
# Allow non-owner read to non-hidden and non-snap files and directories
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this documented anywhere public? The home interface is probably the most used interface we have.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet-- this is new :) I will update the documentation for it, yes.

@codecov-io
Copy link

codecov-io commented Apr 9, 2018

Codecov Report

Merging #5016 into master will increase coverage by 0.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #5016      +/-   ##
==========================================
+ Coverage   78.92%   78.94%   +0.01%     
==========================================
  Files         494      494              
  Lines       37272    37283      +11     
==========================================
+ Hits        29417    29432      +15     
+ Misses       5483     5480       -3     
+ Partials     2372     2371       -1
Impacted Files Coverage Δ
interfaces/builtin/home.go 100% <100%> (ø) ⬆️
overlord/ifacestate/handlers.go 65.09% <0%> (+0.45%) ⬆️
overlord/hookstate/hookmgr.go 75.89% <0%> (+1.02%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 17d355a...bf86a34. Read the comment docs.

Copy link
Collaborator

@zyga zyga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, the interface{} code behaves correctly. The Attr vs Attrs[] is up to you to tweak.

@jdstrand
Copy link
Author

@niemeyer - can you respond to my question: "I did read: [all|owner] instead of read: [all|own]. If you prefer own over owner, let me know and I'll adjust".

Copy link
Contributor

@niemeyer niemeyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per conversation on IRC, we've agreed to drop the "owner" variant and take it only as the default, which is what everybody already uses today. That means no confusion about what the additional syntax means.

Also, a minor detail here:

// It's fine if 'read' isn't specified, but if it is, it needs to be
// either 'all' or 'owner'
if r, ok := plug.Attrs["read"]; ok {
_, ok = r.(string)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic is safe, but there's another gotcha here in terms of the actual intention behind the logic: this check ignores incorrect values that have the wrong type.

We can both fix that and make the logic simpler by folding both conditionals into:

if r, ok := plug.Attrs["read"]; ok && r != "all" {
... error ...
}

@jdstrand
Copy link
Author

All comments are addressed in what I just committed today.

@jdstrand jdstrand added this to the 2.33 milestone May 23, 2018
@jdstrand
Copy link
Author

Since there are two approvals and I addressed @niemeyer comments, merging.

@jdstrand jdstrand merged commit ea39d52 into snapcore:master May 23, 2018
@jdstrand jdstrand deleted the home-nonowner-read branch May 29, 2018 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants